package com.zzw.spring.ioc;

import com.zzw.spring.annotation.AutoWired;
import com.zzw.spring.annotation.Component;
import com.zzw.spring.annotation.ComponentScan;
import com.zzw.spring.annotation.Scope;
import com.zzw.spring.processor.BeanPostProcessor;
import com.zzw.spring.processor.InitializingBean;
import org.apache.commons.lang.StringUtils;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 韩顺平
 * @version 1.0
 * HspSpringApplicationContext 类的作用类似Spring原生ioc容器
 */
public class ZzwSpringApplicationContext {
    private Class configClass;

    //定义属性BeanDefinitionMap -> 存放BeanDefinition对象
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap
            = new ConcurrentHashMap<>();
    //定义属性SingletonObjects - > 用来存放单例对象
    private ConcurrentHashMap<String,Object> singletonObjects = new ConcurrentHashMap<>();

    //定义一个属性beanPostProcessorList=>存放我们的后置处理器[]
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();



    //构造器
    public ZzwSpringApplicationContext(Class configClass) {
        //完成了扫描指定的包
        BeanDefinitinByScan(configClass);

        //通过benaDefinitionMap来初始化singletonObjcts单例池，可以封装为方法
        //遍历我们的beanFefinitionMap中所有的beanDefinition对象
        //这里是java基础-》集合和枚举
        Enumeration<String> keys = beanDefinitionMap.keys();
        while(keys.hasMoreElements()){
            //得到beanName
            String beanName = keys.nextElement();
            //通过beanName，得到对应的beanDefinition对象
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            //判断该bean是singleton还是prototype
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())){
                //将该bean实例化放入到singletonObjects集合当中
                Object bean = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,bean);
            }

        }


        System.out.println("singletonObjects=" + singletonObjects);
        System.out.println("beanDefinitionMap=" + beanDefinitionMap);


    }

    //该方法完成对指定包的扫描，并将bean信息封装到BeanDefinition对象，再放入到Map
    public void BeanDefinitinByScan(Class configClass){
        this.configClass = configClass;
        //获取要扫描的包
        //1. 先得到HspSpringConfig配置的的@ComponentScan(value = "com.hspedu.spring.component")
        ComponentScan componentScan =
                (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        //2. 通过componentScan的value=> 即要扫描的包
        String path = componentScan.value();
        System.out.println("要扫描的包= " + path);

        //得到要扫描的包下的所有资源(类 .class)
        //1.得到类的加载器->APP类加载器
        ClassLoader classLoader =
                ZzwSpringApplicationContext.class.getClassLoader();
        //2. 通过类的加载器获取到要扫描的包的资源 url=》类似一个路径
        path = path.replace(".", "/");//一定要把. 替换成 /
        URL resource =
                classLoader.getResource(path);
        System.out.println("resource=" + resource);
        //3. 将要加载的资源(.class) 路径下的文件进行遍历=>io
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File f : files) {
                //System.out.println("=====================");
                //System.out.println("=" + f.getAbsolutePath());
                String fileAbsolutePath = f.getAbsolutePath();

                //这里我们只处理.class文件
                if (fileAbsolutePath.endsWith(".class")) {

                    //1. 获取到类名
                    String className =
                            fileAbsolutePath.substring(fileAbsolutePath.lastIndexOf("\\") + 1, fileAbsolutePath.indexOf(".class"));
                    //2. 获取类的完整的路径(全类名)
                    //老师解读 path.replace("/",".") => com.hspedu.spring.component.
                    String classFullName = path.replace("/", ".") + "." + className;

                    //3. 判断该类是不是需要注入容器, 就看该类是不是有注解 @Component @Service..
                    try {
                        Class<?> clazz = classLoader.loadClass(classFullName);
                        if (clazz.isAnnotationPresent(Component.class)) {
                            //如果该类使用了@Component注解，说明是Spring bean
                            System.out.println("这是一个Spring bean =" + clazz +" 类名=" + className);

                            //这里有一些说明
                            //1.为了方便，这里将后置处理器放入到ArrayList集合中，那其实源代码是放在单例池中的】
                            //2.如果我发现是一个后置处理器，就将其放入到beanPostProcessorList方便获取
                            //3.在原生的Spring容器中，对后置处理器还是走的getBean和CerateBean那一套逻辑
                            //4.但是需要我们在singletonObjects中加入更多的业务逻辑
                            //5.因为这里只是为了实现后置处理器的机制，所以做了简化，如果走以前的逻辑也可以，只不过比较麻烦


                            //先判断当前这个class有没有实现BeanPostProcessor机制
                            //说明：这里我们不能够去使用Instanceof 来判断clazz是否实现了BeanPostProcessor
                            //Instanceof只能去判断已经实例化的类，而isAssignableFrom可以判断类本身
                            //原因：clazz不是一个实例对象，而是一个类对象/clazz，使用isAssignableFrom
                            if (BeanPostProcessor.class.isAssignableFrom(clazz)){
                                BeanPostProcessor beanPostProcessor =
                                        (BeanPostProcessor)clazz.newInstance();
                                //放入到beanPostProcessorList中去
                                beanPostProcessorList.add(beanPostProcessor);
                                continue;
                            }

                            //1.先得到component注解
                            Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                            //2.得到配置的value值,如果程序员没有配置value怎么办，后面处理
                            String beanName = componentAnnotation.value();
                            if ("".equals(beanName)){//如果没有配置value
                                //这时将该类的类名首字母小写作为beanName
                                beanName = StringUtils.uncapitalize(className);

                            }


                            //3.封装，将Bean的信息封装到BeanDefinition对象->放入到Bean DefinitionMap中去
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(clazz);
                            //4.获取scope
                            if (clazz.isAnnotationPresent(Scope.class)){
                                //如果配置了scope，获取他配置的值
                                Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                                beanDefinition.setScope(scopeAnnotation.value());
                            }else{
                                //如果没有配置scope，以默认值设置
                                beanDefinition.setScope("singleton");
                            }
                            //将beanDefinition对象放到Map
                            beanDefinitionMap.put(beanName,beanDefinition);



                        }else{
                            //如果该类没有使用@Component，说明不是Spring bean
                            System.out.println("这不是一个Spring bean =" + clazz +" 类名=" + className);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            System.out.println("======================");
        }
    }

    //完成creatbean(BeanDefinition bean)方法，也就是通过反射来生成一个实例
    //说明: 目前我们先简单实现
    private Object createBean(String beanName ,BeanDefinition beanDefinition){

        //得到Bean的Class对象
        Class clazz = beanDefinition.getClazz();

        //使用反射来得到实例
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();
            //这里后续需要去加入依赖注入的业务逻辑！！！

            //1.先遍历当前要创建的对象的所有字段
            for (Field declaredField : clazz.getDeclaredFields()){
                //2.判断这个字段是否有Autowired来修饰
                if (declaredField.isAnnotationPresent(AutoWired.class)){
                    //这里有一个小提示
                    //处理@Autowired的required，很简单
                    //Autowired annotation = declaredField.getAnnotation(Autowired.class)
                    //annotation.required()=>进行qi


                    //3.得到字段的名称
                    String name = declaredField.getName();
                    //4.通过getBean方法来获取要组装的对象
                    Object bean = getBean(name);
                    //5.进行组装,由于属性userDao是private的，所以进行访问的时候，需要进行爆破
                    declaredField.setAccessible(true);
                    declaredField.set(instance,bean);
                }



            }
            System.out.println("=====创建好了Bean=====" + instance);

            //我们在Bean的初始化方法前，调用我们的后置处理器的Before方法
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                //在后置处理器的before方法，可以对我们容器生成的bean实例进行处理，然后返回处理过的bean实例
                //相当于做了一个前置处理
                //原生Spring容器比这个复杂很多
                //这里的作用就是如果后置处理器返回了一个NUll，那么返回原来的insatnce不做改变
                Object current =
                        beanPostProcessor.postProcessBeforeInitialization(instance,beanName);
                if (current != null){
                    instance = current;
                }

            }


            //这里判断是否要执行Bean的初始化方法
            //1.判断我们当前创建的Bean对象是否实现了InitializingBean
            //2.instanceof java基础中讲过，表示判断某个对象的运行类型是不是某个类型或者某个类型的子类型、
            //如果这个Bean实现了这个接口InitializingBean。bean就是这个接口的子类型
            //3.这里就是用到了接口编程这个概念
            if (instance instanceof InitializingBean){
                //将instance转成InitializingBean类型
                try {
                    ((InitializingBean)instance).afterPropertiesSet();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            //在Bean的初始化方法之后调用初始化之后的方法After
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                //在后置处理器的before方法，可以对我们容器生成的bean实例进行处理，然后返回处理过的bean实例
                //相当于做了一个前置处理
                Object current
                        = beanPostProcessor.postProcessAfterInitialization(instance,beanName);
                if (current != null){
                    instance = current;
                }

            }
            System.out.println("===========");


            return instance;


        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        //如果反射和创建对象失败
        return null;

    }


    //编写方法getBean(String name),编写方法返回容器中的对象
    public Object getBean(String name) {
        //加一个判断,判断传入的beanName是否在我们的beanDefinitionmap存在
        if (beanDefinitionMap.containsKey(name)){//如果存在
            BeanDefinition beanDefinition = beanDefinitionMap.get(name);
            //先得到beanDefinition的scope
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())){
                //说明是单例配置，就直接从单例池获取
                return singletonObjects.get(name);
            }else {
                //如果不是单例，就调用createBean反射一个对象返回
                return createBean(name,beanDefinition);
            }
        }else{//如果不存在
            //抛出一个空指针异常-当然也可以自定义异常然后抛出
            throw new NullPointerException();
        }

    }
}

